using System.Diagnostics;
using System.Security.Claims;
using GraphQL.Execution;
using GraphQL.Federation.Resolvers;
using GraphQL.Instrumentation;
using GraphQL.Resolvers;
using GraphQL.Types;
using GraphQL.Validation;
using GraphQLParser.AST;

namespace GraphQL.Federation;

public partial class FederationResolverAttribute
{
    /// <summary>
    /// This federation resolver creates an <see cref="IResolveFieldContext"/> which has a source object
    /// coerced from the entity representation properties provided from Apollo Router, and null arguments.
    /// It then calls the configured field resolver with the context. It is intended to be used for non-static
    /// federation resolvers in a type-first schema.
    /// </summary>
    /// <remarks>
    /// When the field is resolved, the type-first resolver will be called with the context, so when
    /// the resolver generated by <see cref="AutoRegisteringInputObjectGraphType"/> is called, it will
    /// be called on the instance generated from the entity representation properties.
    /// </remarks>
    private class FederationNonStaticResolver : FederationResolverBase
    {
        private readonly FieldType _fieldType;

        public FederationNonStaticResolver(FieldType fieldType, Type sourceType)
        {
            _fieldType = fieldType;
            SourceType = sourceType; // FederationResolverBase will coerce the keys onto the 'source type'
        }

        public override Type SourceType { get; }

        public override ValueTask<object?> ResolveAsync(IResolveFieldContext context, IComplexGraphType graphType, object source)
        {
            var context2 = new Context(context, source);
            var resolver = _fieldType.Resolver ?? ThrowForNoResolver();
            return resolver.ResolveAsync(context2);

            [DoesNotReturn]
            [StackTraceHidden]
            IFieldResolver ThrowForNoResolver()
                => throw new InvalidOperationException($"The field resolver for {_fieldType.Name} must be set on the field type.");
        }

        private class Context : IResolveFieldContext
        {
            private readonly IResolveFieldContext _context;

            public Context(IResolveFieldContext context, object source)
            {
                _context = context;
                Source = source;
            }

            public GraphQLField FieldAst => _context.FieldAst;
            public FieldType FieldDefinition => _context.FieldDefinition;
            public IObjectGraphType ParentType => _context.ParentType;
            public IResolveFieldContext? Parent => _context.Parent;
            public IDictionary<string, ArgumentValue>? Arguments => null;
            public IDictionary<string, DirectiveInfo>? Directives => _context.Directives;
            public object? RootValue => _context.RootValue;
            public object? Source { get; }
            public ISchema Schema => _context.Schema;
            public GraphQLDocument Document => _context.Document;
            public GraphQLOperationDefinition Operation => _context.Operation;
            public Variables Variables => _context.Variables;
            public CancellationToken CancellationToken => _context.CancellationToken;
            public Metrics Metrics => _context.Metrics;
            public ExecutionErrors Errors => _context.Errors;
            public IEnumerable<object> Path => _context.Path;
            public IEnumerable<object> ResponsePath => _context.ResponsePath;
            public Dictionary<string, (GraphQLField Field, FieldType FieldType)>? SubFields => _context.SubFields;
            public IReadOnlyDictionary<string, object?> InputExtensions => _context.InputExtensions;
            public IDictionary<string, object?> OutputExtensions => _context.OutputExtensions;
            public IServiceProvider? RequestServices => _context.RequestServices;
            public IExecutionArrayPool ArrayPool => _context.ArrayPool;
            public ClaimsPrincipal? User => _context.User;
            public IDictionary<string, object?> UserContext => _context.UserContext;
            public IExecutionContext ExecutionContext => _context.ExecutionContext;
        }
    }
}
